Skip to content

feat: add OpenAI Responses API endpoint (/responses)#1

Merged
SlushEE0 merged 1 commit into
masterfrom
copilot/migrate-to-openai-responses-api
Apr 22, 2026
Merged

feat: add OpenAI Responses API endpoint (/responses)#1
SlushEE0 merged 1 commit into
masterfrom
copilot/migrate-to-openai-responses-api

Conversation

Copy link
Copy Markdown

Copilot AI commented Apr 21, 2026

Adds POST /responses and POST /v1/responses endpoints that proxy GitHub Copilot's native /responses endpoint, implementing the OpenAI Responses API alongside the existing Chat Completions API.

New files

  • src/services/copilot/create-responses.ts — fetches ${copilotBaseUrl}/responses; full TypeScript types for ResponsesPayload, ResponseObject, OutputItem, etc.; sets X-Initiator and vision headers using the same logic as the completions service
  • src/routes/responses/handler.ts — auto-fills max_output_tokens from model capabilities, respects manualApprove/rate-limiting, streams or returns the response
  • src/routes/responses/route.ts — Hono router with error forwarding

Server

/responses and /v1/responses mounted in src/server.ts, mirroring the existing dual-mount pattern for completions.

Usage

POST /v1/responses
{
  "model": "gpt-4o",
  "input": [{ "role": "user", "content": "Hello" }],
  "stream": true
}

Response shape: { "object": "response", "output": [...] } with SSE events for streaming.

@SlushEE0 SlushEE0 marked this pull request as ready for review April 22, 2026 01:38
Copilot AI review requested due to automatic review settings April 22, 2026 01:38
@SlushEE0 SlushEE0 merged commit 94d3474 into master Apr 22, 2026
1 check passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds OpenAI-compatible Responses API support by proxying GitHub Copilot’s native /responses endpoint and exposing it at both /responses and /v1/responses.

Changes:

  • Introduces a new Copilot service (createResponses) to call ${copilotBaseUrl}/responses, including type definitions and header logic (X-Initiator + vision).
  • Adds a new Hono route + handler for /responses that applies rate-limiting/manual approval, auto-fills max_output_tokens, and supports SSE streaming.
  • Mounts the new routes in src/server.ts for both unversioned and /v1 prefixes.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/services/copilot/create-responses.ts New Copilot proxy client for /responses, including payload/response type definitions and streaming support.
src/routes/responses/handler.ts Implements request handling (rate limit, manual approval, token limit defaulting, streaming/non-streaming responses).
src/routes/responses/route.ts Adds Hono router wiring for the new endpoint with error forwarding.
src/server.ts Mounts /responses and /v1/responses routes alongside existing endpoints.
bun.lock Lockfile updated (adds configVersion).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

await checkRateLimit(state)

let payload = await c.req.json<ResponsesPayload>()
consola.debug("Request payload:", JSON.stringify(payload).slice(-400))
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verbose debug logging prints portions of the raw request payload, which can include user prompts, tool arguments, or image/file URLs. Consider redacting sensitive fields (e.g., input, instructions, metadata) or logging only high-level metadata (model, stream flag, token limits) to avoid accidental disclosure when --verbose is enabled.

Suggested change
consola.debug("Request payload:", JSON.stringify(payload).slice(-400))
consola.debug("Request metadata:", {
model: payload.model,
stream: payload.stream,
max_output_tokens: payload.max_output_tokens,
})

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +55
for await (const chunk of response) {
consola.debug("Streaming chunk:", JSON.stringify(chunk))
if (!chunk.data) continue
await stream.writeSSE({
event: chunk.event,
data: chunk.data,
})
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The streaming path logs every SSE chunk (consola.debug("Streaming chunk"...)). For long responses this can generate very large logs and add significant overhead in verbose mode. Consider sampling, truncating, or logging only event types/byte sizes instead of the full chunk payload.

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +27
export const createResponses = async (payload: ResponsesPayload) => {
if (!state.copilotToken) throw new Error("Copilot token not found")

const enableVision =
Array.isArray(payload.input)
&& payload.input.some(
(x) =>
Array.isArray(x.content)
&& x.content.some((part) => part.type === "input_image"),
)

const isAgentCall =
Array.isArray(payload.input)
&& payload.input.some((msg) => ["assistant", "tool"].includes(msg.role))

const headers: Record<string, string> = {
...copilotHeaders(state, enableVision),
"X-Initiator": isAgentCall ? "agent" : "user",
}

Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new /responses service adds logic for X-Initiator and vision header selection, but there are no tests covering these behaviors (unlike create-chat-completions, which has tests/create-chat-completions.test.ts). Add unit tests for createResponses to verify X-Initiator switches to agent when input contains assistant/tool, and that copilot-vision-request is set when input includes an input_image part.

Suggested change
export const createResponses = async (payload: ResponsesPayload) => {
if (!state.copilotToken) throw new Error("Copilot token not found")
const enableVision =
Array.isArray(payload.input)
&& payload.input.some(
(x) =>
Array.isArray(x.content)
&& x.content.some((part) => part.type === "input_image"),
)
const isAgentCall =
Array.isArray(payload.input)
&& payload.input.some((msg) => ["assistant", "tool"].includes(msg.role))
const headers: Record<string, string> = {
...copilotHeaders(state, enableVision),
"X-Initiator": isAgentCall ? "agent" : "user",
}
export const responsesPayloadHasImageInput = (payload: ResponsesPayload) =>
Array.isArray(payload.input)
&& payload.input.some(
(message) =>
Array.isArray(message.content)
&& message.content.some((part) => part.type === "input_image"),
)
export const responsesPayloadIsAgentCall = (payload: ResponsesPayload) =>
Array.isArray(payload.input)
&& payload.input.some((message) => ["assistant", "tool"].includes(message.role))
export const createResponsesHeaders = (payload: ResponsesPayload) => {
const enableVision = responsesPayloadHasImageInput(payload)
const isAgentCall = responsesPayloadIsAgentCall(payload)
return {
...copilotHeaders(state, enableVision),
"X-Initiator": isAgentCall ? "agent" : "user",
}
}
export const createResponses = async (payload: ResponsesPayload) => {
if (!state.copilotToken) throw new Error("Copilot token not found")
const headers = createResponsesHeaders(payload)

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants